/*
 * Copyright (C) 2011-2012 Andrew Wells
 *
 * Licensed under the GNU License, Version 3
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.gnu.org/copyleft/gpl.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * Dungeons & Dragons Copyright (C) Wizards of the Coast. I do not claim
 * the right to any information used from their books or resources.
 */

package com.trace.wells.charactertracker;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Random;
import java.util.UUID;

public class Character implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public int overallMagicType;
	public CharacterClass characterClass;
	public Abilities abilities;
	public Info info;
	//TODO: MAKE A 2D array(list?)!!
	public ArrayList[] spells;
	public ArrayList<Equipment> equipment;
	public ArrayList[] items;
	private int uniqueId;

	private boolean skillsAssigned = false;

	Character() {
		characterClass = new CharacterClass();
		abilities = new Abilities();
		info = new Info();
		spells = new ArrayList[ConstantHelper.CLASS_NUMBER_MAX];
		items = new ArrayList[ConstantHelper.NUMBER_OF_ITEM_TYPES_MAX];

		for (int i = 0; i < spells.length; i++) {
			spells[i] = new ArrayList<Spell>();
		}

		equipment = new ArrayList<Equipment>();
		for (int i = 0; i < ConstantHelper.NUMBER_OF_ITEM_TYPES_MAX; i++) {
			items[i] = new ArrayList<Item>();
		}

		Random r = new Random();
		uniqueId = r.nextInt(10000);
	}

	public boolean equals(Character ch) {
		boolean same = false;
		String lname, rname, lrace, rrace;
		lname = this.info.getName();
		rname = ch.info.getName();
		lrace = this.info.getRace();
		rrace = ch.info.getRace();

		if (lname.contentEquals(rname) && lrace.contentEquals(rrace)) {
			same = true;
		}
		return same;
	}

	/**
	 * This function should be called only when a new character is created. It
	 * sets the level of the character to 1 and calculate all other basic
	 * information.
	 * 
	 * @param charClass
	 * @param charRace
	 * @param name
	 */
	public void initializeCharacter(String name, String charRace, String charClass) {

		this.info.setName(name);
		this.info.setRace(charRace);
		this.info.applySizeBonus(charRace);

		characterClass = new CharacterClass(charClass, 1, this.abilities.getModArray(), this.info.getBonusSize());

		///////////////////////////////////////
		this.info.setTotalHP(this.characterClass.getHitDieForClass(charClass));
		this.info.setCurrentHP(this.characterClass.getHitDieForClass(charClass));

		this.info.initializeGroupExpandedElement();

		this.info.setExperience(0);
		//Set player's health
		//		this.info.setHealth(charClass);

		//this.info.initializeSpellsCastingStats(this.info.getLevel(), charClass);

		//Sets initiative mod.
		this.info.setInitiativeMod(this.abilities.getDEXMOD());

		//You know what it does...
		calculateAndSetArmorClass();

		overallMagicType = characterClass.calculateOverallCharacterClassMagicType(characterClass.getCharacterClassLevelsArray());

		//TODO: Call character.info.getSkillPointCount(int level, int intMod) when first going to battle screen and on level ups
	}

	/**
	 * This function will be called whenever the player pushes the level up
	 * button It will re-calculate the character's bonuses, number of spells
	 * that are castable (if applicable).
	 */
	public void levelUp(int additionalHealth, String charClass) {

		this.characterClass.levelUpCharacterClass(charClass, this.abilities.getModArray(), this.info.getBonusSize());

		this.info.increaseHealth(additionalHealth, this.abilities.getCONMOD());

		if (this.characterClass.getLevel() % 4 == 0) {
			//TODO: THIS SHOULD BE DONE IN THE VIEW!!
		}

		//calculates And Sets Armor Class
		calculateAndSetArmorClass();

		//Update any weapon information that needs to be updated
		updateAllWeaponStats();

		overallMagicType = characterClass.calculateOverallCharacterClassMagicType(characterClass.getCharacterClassLevelsArray());

		//TODO: Call character.info.getSkillPointCount(int level, int intMod) when first going to battle screen and on level ups
	}

	/**
	 * Calculates the amount of armor you have on and factors it into your AC
	 * 
	 */
	public void calculateAndSetArmorClass() {

		//TODO: ACCOUT FOR SPELL FAILURE. DISPLAY IN SPELL VIEW.

		//AC = 10 + armor bonus + shield bonus + Dexterity modifier + size modifier
		int armorClassBase = 10;
		int armorClassAddition = 0;
		//Loop through equipment and add together all the AC each item has

		int minArmorDex = getMinArmorDex();
		int curDex = this.abilities.getDEXMOD();
		int usedDex;

		//This takes into account the dexterity penalty associated with wearing armor
		if (minArmorDex == -1)
			usedDex = curDex;
		if (curDex > minArmorDex) {
			usedDex = minArmorDex;
		} else {
			usedDex = curDex;
		}

		//Add dexterity, size, and wron armor modifier
		armorClassAddition += usedDex + this.info.getBonusSize() + getArmorRatingWorn();

		this.info.setArmorClass((armorClassBase + armorClassAddition));
	}

	/**
	 * Returns an integer representing an armor rating of all the armor equipped
	 * by the character
	 * 
	 * @return
	 */
	public int getArmorRatingWorn() {
		int retVal = 0;
		for (Equipment armor : equipment) {
			if (armor.getEquipmentType() == ConstantEquipmentHelper.EQUIP_TYPE_ARMOR) {
				Armor a = (Armor) armor;
				if (a.isEquipped())
					retVal += a.getArmorShieldBonus();
			}
		}
		return retVal;
	}

	public int getMinArmorDex() {
		int retVal = 300;
		for (Equipment armor : equipment) {
			if (armor.getEquipmentType() == ConstantEquipmentHelper.EQUIP_TYPE_ARMOR) {
				Armor a = (Armor) armor;
				if (a.isEquipped() && a.getMaxDexBonus() < retVal)
					retVal = a.getMaxDexBonus();
			}
		}
		//If no armor was equipped, set it to 0;
		if (retVal == 300)
			retVal = -1;
		return retVal;
	}

	public Weapon getWeaponWithName(String name) {
		Weapon returnWeapon = null;
		for (Equipment e : equipment) {
			if (e instanceof Weapon)
				if (e.getName().equalsIgnoreCase(name)) {
					returnWeapon = (Weapon) e;
				}
		}
		return returnWeapon;
	}

	/**
	 * @return the uniqueId
	 */
	public int getUniqueId() {
		return uniqueId;
	}

	/**
	 * @param uniqueId
	 *            the uniqueId to set
	 */
	public void setUniqueId(int uniqueId) {
		this.uniqueId = uniqueId;
	}

	/**
	 * @return the skillsAssigned
	 */
	public boolean isSkillsAssigned() {
		return skillsAssigned;
	}

	/**
	 * @param skillsAssigned
	 *            the skillsAssigned to set
	 */
	public void setSkillsAssigned(boolean skillsAssigned) {
		this.skillsAssigned = skillsAssigned;
	}

	//	public ArrayList<Spell> getSpecificLevelSpells(int level) {
	//		ArrayList<Spell> spells = new ArrayList<Spell>();
	//		for (Spell s : this.spells) {
	//			if (s.getSpellLevel() == level) {
	//				spells.add(s);
	//			}
	//		}
	//		return spells;
	//	}

	//	/**
	//	 * Gets the current number of spells known for each level
	//	 * 
	//	 * @return an array of ingegers
	//	 */
	//	public int[] getNumberSpellsKnown() {
	//		int[] spellsKnown = new int[10];
	//		for (Spell s : spells) {
	//			int level = s.getSpellLevel();
	//			spellsKnown[level]++;
	//		}
	//		return spellsKnown;
	//	}

	/**
	 * Returns the spells for the specified character Array
	 * 
	 * @return the spells
	 */
	@SuppressWarnings("unchecked")
	public ArrayList<Spell> getSpells(int position) {
		return spells[position];
	}

	/**
	 * @param spells
	 *            the spells to set
	 */
	public void setSpells(ArrayList<Spell> spells, int position) {
		this.spells[position] = spells;
	}

	public boolean checkLevelUp(int level) {
		boolean needLeveling = false;
		if (info.getExperience() > info.getExpToNextLevel(level)) {
			needLeveling = true;
		}
		return needLeveling;
	}

	public void removeSpellFromClass(String spellName, int currentSpellBookView) {
		ArrayList<Spell> temporarySpellArray = spells[currentSpellBookView];
		for (int i = 0; i < temporarySpellArray.size(); i++) {
			Spell index = temporarySpellArray.get(i);
			if (index.getName().equalsIgnoreCase(spellName)) {
				temporarySpellArray.remove(index);
				break;
			}
		}
		spells[currentSpellBookView] = temporarySpellArray;
	}

	/**
	 * Will return a spell in the character's spell list based on the name and
	 * spellbookPosition passed
	 * 
	 * Will return null if the name is not found getSpellBasedOnName
	 * 
	 * @param spellname
	 * @param currentSpellBookView
	 * @return
	 */
	public Spell getSpellBasedOnName(String spellname, int currentSpellBookView) {
		ArrayList<Spell> tempSpellList = spells[currentSpellBookView];
		Spell returnSpell = null;
		for (Spell s : tempSpellList) {
			if (s.getName().equalsIgnoreCase(spellname)) {
				returnSpell = s;
			}
		}
		return returnSpell;
	}

	public boolean hasFreeSpellsKnownSlots(int currentSpellBookView) {
		boolean hasFreeSlots = false;
		ArrayList<Spell> tempSpellList = spells[currentSpellBookView];
		int[] tempCharacterClassLevelArray = characterClass.getCharacterClassLevelsArray();
		int[] spellLevelStorageArray = new int[ConstantHelper.SPELL_LEVEL_MAX];

		int classLevel = tempCharacterClassLevelArray[currentSpellBookView];

		int[] tempMaxSpellsKnownArray;

		if (currentSpellBookView == 1)
			tempMaxSpellsKnownArray = ConstantHelper.SPELLS_KNOWN_BARD[classLevel];
		else
			tempMaxSpellsKnownArray = ConstantHelper.SPELLS_KNOWN_SORCERER[classLevel];

		for (Spell s : tempSpellList) {
			spellLevelStorageArray[s.getSpellLevel()]++;
		}
		for (int i = 0; i < tempMaxSpellsKnownArray.length; i++) {
			if (tempMaxSpellsKnownArray[i] > spellLevelStorageArray[i])
				hasFreeSlots = true;
		}

		return hasFreeSlots;
	}

	/**
	 * @return the overallMagicType
	 */
	public int getOverallMagicType() {
		return overallMagicType;
	}

	/**
	 * @param overallMagicType
	 *            the overallMagicType to set
	 */
	public void setOverallMagicType(int overallMagicType) {
		this.overallMagicType = overallMagicType;
	}

	public boolean spellSlotIsFree(int classLevel, int currentSpellBookView, int spellLevel) {
		boolean spellSlotIsFree = false;
		ArrayList<Spell> tempSpellList = spells[currentSpellBookView];
		int[] spellLevelStorageArray = new int[ConstantHelper.SPELL_LEVEL_MAX];
		int maxSpellSlotNumber;
		if (currentSpellBookView == ConstantHelper.CLASS_NUMBER_BARD)
			maxSpellSlotNumber = ConstantHelper.SPELLS_KNOWN_BARD[classLevel][spellLevel];
		else
			maxSpellSlotNumber = ConstantHelper.SPELLS_KNOWN_SORCERER[classLevel][spellLevel];

		for (Spell s : tempSpellList) {
			spellLevelStorageArray[s.getSpellLevel()]++;
		}

		if (spellLevelStorageArray[spellLevel] < maxSpellSlotNumber)
			spellSlotIsFree = true;

		return spellSlotIsFree;
	}

	public int getSpecificClassLevel(int currentSpellBookView) {
		int[] tempCharClassArray = characterClass.getCharacterClassLevelsArray();
		return tempCharClassArray[currentSpellBookView];
	}

	public Weapon getActiveWeapon() {
		Weapon returnWeapon = null;
		for (Equipment e : equipment) {
			if (e instanceof Weapon)
				if (((Weapon) e).isEquipped()) {
					returnWeapon = (Weapon) e;
					break;
				}
		}
		return returnWeapon;
	}

	public void unequipAllWeapons() {
		for (Equipment e : equipment) {
			if (e instanceof Weapon) {
				((Weapon) e).setEquipped(false);
			}
		}
	}

	public void decrementUsesForItem(Item item) {
		for (Equipment e : equipment) {
			if (e instanceof Item) {
				if (e.equals(item))
					((Item) e).decrementUsesRemaining();
			}
		}
	}

	public void updateAllWeaponStats() {
		for (Equipment e : equipment) {
			if (e instanceof Weapon) {
				String bonusStringToUse;
				if (((Weapon) e).isRanged())
					bonusStringToUse = characterClass.getRangedAttackBonusString();
				else
					bonusStringToUse = characterClass.getMeleeAttackBonusString();
				((Weapon) e).calculateAndSetAttackAndRollBonus(bonusStringToUse, abilities.getSTRMOD(), abilities.getDEXMOD());
			}
		}
	}

	public void removeWeaponByName(String name) {
		for (Equipment e : equipment) {
			if (e instanceof Weapon) {
				if (e.getName().equalsIgnoreCase(name))
					equipment.remove(e);
				break;
			}
		}
	}

	/**
	 * @return the items
	 */
	public ArrayList<Item>[] getItems() {
		return items;
	}

	/**
	 * @param items
	 *            the items to set
	 */
	public void setItems(ArrayList<Item>[] items) {
		this.items = items;
	}

	public String getFileIdentifier() {
		String returnString = this.info.getName() + String.valueOf(uniqueId);
		return returnString;
	}

	public String getCharacterArrayInformationString() {
		String tempArray = this.info.getName() + ":" + String.valueOf(this.characterClass.getLevel()) + ":" + this.info.getRace() + ":" + this.characterClass.getClassDescriptionString() + ":"
				+ String.valueOf(this.getUniqueId());
		return tempArray;
	}

	//TODO: Upon level up, notify user of appropriate changes needed for leveling:
	// Upgrade Health, Upgrade spells per day...
}
